[Anima] Add img2img pipeline blocks#13929
Conversation
|
thanks @PreethamNoelP, can we start with updating the PR with a code snippet to test it and the images to see that it works before doing the review. |
|
Hi @asomoza, here is the code snippet and output images. Usage: |
| # Copied from diffusers.modular_pipelines.anima.before_denoise.AnimaSetTimestepsStep | ||
| class AnimaImg2ImgSetTimestepsStep(ModularPipelineBlocks): |
There was a problem hiding this comment.
you used the copied from here but you changed the function no? You can catch these errors and the code quality by following the contributing doc
There was a problem hiding this comment.
Removed the # Copied from tag above AnimaImg2ImgSetTimestepsStep.
Since the class intentionally skips the set_begin_index(0) call (that's handled later by the VAE encoder step), it was never truly identical to the source, so the tag shouldn't have been there.
Please let me know if any further changes are required.
|
|
||
|
|
||
| # auto_docstring | ||
| class AnimaImg2ImgAutoBlocks(SequentialPipelineBlocks): |
There was a problem hiding this comment.
we do not need a AnimaImg2ImgAutoBlocks. We add a workflow into the AnimaAutoBlocks
There was a problem hiding this comment.
Removed AnimaImg2ImgAutoBlocks entirely and folded img2img into AnimaAutoBlocks following the z_image pattern.
I added AnimaAutoDenoiseStep (an AutoPipelineBlocks that picks between img2img and txt2img based on whether image is provided) and AnimaImg2ImgDenoiseStep (a SequentialPipelineBlocks that wraps the img2img-specific set_timesteps + denoise steps).
AnimaAutoBlocks now has a _workflow_map covering both workflows, so users only need one pipeline - passing image= automatically triggers img2img.
Please let me know if any further changes are required.
…nto AnimaAutoBlocks - Remove incorrect `# Copied from` comment above AnimaImg2ImgSetTimestepsStep - Delete AnimaImg2ImgAutoBlocks; introduce AnimaAutoDenoiseStep (AutoPipelineBlocks) and AnimaImg2ImgDenoiseStep (SequentialPipelineBlocks) so img2img lives as a workflow inside AnimaAutoBlocks, following the z_image pattern - Update __init__.py, dummy_objects, and docs to remove AnimaImg2ImgAutoBlocks - Update img2img test to use AnimaAutoBlocks with updated workflow block paths
|
@PreethamNoelP thanks, sorry I missed the update, I checked it and it looks good to me, I tested it and the outputs seems coherent too. @yiyixuxu can you review it one last time to see if I missed something? |
|
@bot /style |
|
Style bot fixed some files and pushed the changes. |
|
The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update. |
|
Any updates on this by chance? Would love to see this in |
| ) | ||
|
|
||
| @property | ||
| def inputs(self) -> list[InputParam]: |
There was a problem hiding this comment.
the encoder step should just take an image (height/width too) and return image_latents,
i.e. if you have already encoded the image once, you should not need to encode again just to use a different strength/num_images_per_prompt or num_inference_steps etc
There was a problem hiding this comment.
Hi @yiyixuxu, thank you for the suggestion. AnimaImg2ImgVaeEncoderStep has been refactored to only handle image encoding, returning image_latents. The timestep slicing and noise mixing logic has been extracted into a new AnimaImg2ImgPrepareLatentsStep, following the same separation as ZImageVaeImageEncoderStep and ZImagePrepareLatentswithImageStep.
|
|
||
| block_classes = [AnimaImg2ImgDenoiseStep, AnimaCoreDenoiseStep] | ||
| block_names = ["img2img", "text2image"] | ||
| block_trigger_inputs = ["image", None] |
There was a problem hiding this comment.
| block_trigger_inputs = ["image", None] | |
| block_trigger_inputs = ["image_latents", None] |
There was a problem hiding this comment.
Hi @yiyixuxu, thank you for the suggestion. AnimaAutoDenoiseStep has been updated to use block_trigger_inputs = ["image_latents", None]. Additionally, AnimaAutoVaeImageEncoderStep (triggered by "image") has been added as a sequential step in AnimaAutoBlocks, ensuring image_latents is placed in state before the denoise step selects the appropriate block - consistent with the ZImageAutoBlocks structure.
…ImageEncoderStep - AnimaImg2ImgVaeEncoderStep now only encodes image → image_latents - AnimaImg2ImgPrepareLatentsStep handles timestep slicing + noise mixing - AnimaAutoVaeImageEncoderStep (AutoPipelineBlocks, trigger: image) added to AnimaAutoBlocks - AnimaAutoDenoiseStep trigger changed from image to image_latents - Follows z_image pattern (ZImageVaeImageEncoderStep + ZImagePrepareLatentswithImageStep)
| block_state.timesteps, block_state.num_inference_steps = get_timesteps( | ||
| components.scheduler, block_state.num_inference_steps, block_state.strength | ||
| ) |
There was a problem hiding this comment.
can we move this into AnimaImg2ImgSetTimestepsStep? makes more sense there
There was a problem hiding this comment.
Hi @yiyixuxu, thank you for the suggestion.
The get_timesteps() call has been moved into AnimaImg2ImgSetTimestepsStep, which now computes the full schedule and slices it by strength in a single step. AnimaImg2ImgPrepareLatentsStep no longer touches the timestep schedule.
| components.scheduler, block_state.num_inference_steps, block_state.strength | ||
| ) | ||
|
|
||
| total_batch = block_state.batch_size * block_state.num_images_per_prompt |
There was a problem hiding this comment.
in modular, we should expand the image_latent in the input step , you can add an image_input step similar to https://github.com/huggingface/diffusers/blob/main/src/diffusers/modular_pipelines/anima/before_denoise.py#L196
There was a problem hiding this comment.
Hi @yiyixuxu, thank you for the suggestion.
A new AnimaImageInputStep has been added following the same pattern as AnimaTextInputStep - it expands image_latents to batch_size * num_images_per_prompt via repeat_tensor_to_batch_size and derives height/width from the latents when not provided. AnimaImg2ImgPrepareLatentsStep now only handles noise generation and scale_noise().
| return [OutputParam.template("latents")] | ||
|
|
||
|
|
||
| # auto_docstring |
There was a problem hiding this comment.
why do we need this? can you just add the set_timesteps as part of core_denoise?
There was a problem hiding this comment.
Hi @yiyixuxu, you're right - the wrapper was left over from an earlier structure and is no longer needed. AnimaImg2ImgDenoiseStep has been removed, and AnimaImg2ImgSetTimestepsStep is now part of AnimaImg2ImgCoreDenoiseStep directly, making it a flat sequence (text_conditioning → input → image_input → set_timesteps → prepare_latents → denoise) consistent with ZImageImage2ImageCoreDenoiseStep.
… image input step, flatten img2img core denoise
|
|
||
|
|
||
| # auto_docstring | ||
| class AnimaAutoDenoiseStep(AutoPipelineBlocks): |
There was a problem hiding this comment.
| class AnimaAutoDenoiseStep(AutoPipelineBlocks): | |
| class AnimaAutoCoreDenoiseStep(AutoPipelineBlocks): |
There was a problem hiding this comment.
Hi @yiyixuxu, thank you for the suggestion.
AnimaAutoDenoiseStep has been renamed to AnimaAutoCoreDenoiseStep, consistent with the naming used across the other modular pipelines.
| enable_full_determinism() | ||
|
|
||
|
|
||
| ANIMA_IMG2IMG_WORKFLOWS = { |
There was a problem hiding this comment.
There was a problem hiding this comment.
Hi @yiyixuxu, done - the img2img test class has been moved into test_modular_pipeline_anima.py and the separate test file has been removed.
…enoiseStep, merge img2img tests into main test file

What does this PR do?
This PR adds image-to-image (
img2img) support to the Anima modular pipeline, as requested in #13903. The implementation follows the z_image modular pattern: img2img is a workflow inside the unifiedAnimaAutoBlocks, selected automatically when animage(or pre-encodedimage_latents) is provided.Design
New blocks
AnimaImg2ImgVaeEncoderStep— preprocesses the input image and encodes it with the VAE, returningimage_latentsonly. Encoding is independent ofstrength/num_inference_steps/num_images_per_prompt, so latents encoded once can be reused across settings.AnimaImageInputStep— input processing step (analogous toAnimaTextInputStep) that expandsimage_latentsto the final denoising batch (batch_size * num_images_per_prompt) and derivesheight/widthfrom the latents when not provided.AnimaImg2ImgSetTimestepsStep— computes the full timestep schedule, then slices it bystrengthviaget_timesteps(), which also sets the scheduler's begin index.AnimaImg2ImgPrepareLatentsStep— generates noise and mixes it with the image latents viascheduler.scale_noise()at the first sliced timestep, and creates the Cosmos padding mask.AnimaImg2ImgCoreDenoiseStep— flatSequentialPipelineBlocks:text_conditioning → input → image_input → set_timesteps → prepare_latents → denoise, mirroringZImageImage2ImageCoreDenoiseStep.AnimaAutoVaeImageEncoderStep—AutoPipelineBlockstriggered byimage; skipped for text2image.AnimaAutoDenoiseStep—AutoPipelineBlockswithblock_trigger_inputs = ["image_latents", None], selecting img2img or text2image core denoise.Unified pipeline
AnimaAutoBlocks=text_encoder → vae_encoder (auto) → denoise (auto) → decode, with a_workflow_mapcovering bothtext2imageandimg2img. No separate img2img pipeline class.Usage
Test results
Fixes #13903
Before submitting
Who can review?
@asomoza @yiyixuxu